"""
__author__ = 'loong'
"""
import os
from baye import structures
from baye import models
from baye import util

from PIL import Image as PImage


input_scale = 1
output_scale = 1


def scale(i):
    return i // input_scale * output_scale


class Animation:
    class meta:
        frames = 'frames'
        images = 'images'
        spe = 'spe'

    def __init__(self, data=None):
        self.type = 0
        self.idx = 0
        self.count = 0
        self.picmax = 0
        self.startfrm = 0
        self.endfrm = 0

        self.frames = []
        self.images = []
        if data is not None:
            self.load_data(data)

    def load_data(self, data):
        self.load_bin(structures.DataIO(data=data))

    def load_bin(self, io):
        info = io.read_struct(models.SPERES)
        self.__dict__.update(info)
        for _ in range(self.count):
            frm = AnimationFrame()
            frm.load_bin(io)
            self.frames.append(frm)

        while not io.eof():
            image = ImageSet()
            image.load_bin(io)
            self.images.append(image)

    def write_bin(self, io):
        io.write_struct(models.SPERES, self)
        for frame in self.frames:
            frame.write_bin(io)
        for image in self.images:
            image.write_bin(io)

    def bin(self):
        io = structures.DataIO(b'')
        self.write_bin(io)
        return io.getvalue()

    def yield_images(self):
        for i, img in enumerate(self.images):
            for i0, i1, image in img.yield_images():
                yield i, i0, i1, image

    def save(self, resdir):
        xml = util.ET.Element(self.meta.spe)
        xml.attrib['type'] = str(self.type)
        xml.attrib['idx'] = str(self.idx)
        xml.attrib['count'] = str(self.count)
        xml.attrib['picmax'] = str(self.picmax)
        xml.attrib['startfrm'] = str(self.startfrm)
        xml.attrib['endfrm'] = str(self.endfrm)
        frames = util.ET.Element(self.meta.frames)
        xml.append(frames)
        for frame in self.frames:
            frames.append(frame.to_xml())
        if not os.path.exists(resdir):
            os.makedirs(resdir)

        images_node = util.ET.Element(self.meta.images)
        xml.append(images_node)
        imgn = 0
        for image in self.images:
            x = image.to_xml()
            image_name = 'image{}'.format(imgn)
            imgn += 1
            x.attrib['name'] = image_name
            images_node.append(x)
            image.save_images(os.path.join(resdir, image_name) + '_')
        raw = util.pretty_print_xml(xml)
        open(os.path.join(resdir, 'meta.xml'), 'wb').write(raw)

    def load_assets(self, resdir):
        xml = util.ET.fromstring(open(os.path.join(resdir, 'meta.xml')).read())
        self.load_xml(xml, resdir)
        return self

    @util.dec_raise_xml
    def load_xml(self, xml, resdir):
        self.type = int(xml.attrib['type'])
        self.idx = int(xml.attrib['idx'])
        self.count = int(xml.attrib['count'])
        self.picmax = int(xml.attrib['picmax'])
        self.startfrm = int(xml.attrib['startfrm'])
        self.endfrm = int(xml.attrib['endfrm'])
        for node in xml:
            if node.tag == self.meta.frames:
                for fnode in node:
                    frame = AnimationFrame().load_xml(fnode)
                    self.frames.append(frame)
            elif node.tag == self.meta.images:
                for inode in node:
                    img = ImageSet().load_xml(inode, os.path.join(resdir, inode.attrib['name']) + '_')
                    self.images.append(img)


class AnimationFrame:
    class meta:
        frame = 'frame'

    def __init__(self):
        self.x = 0
        self.y = 0
        self.cdelay = 0
        self.ndelay = 0
        self.picIdx = 0

    def write_bin(self, io):
        io.write_struct(models.SPEUNIT, self)

    def load_bin(self, io):
        info = io.read_struct(models.SPEUNIT)
        self.__dict__.update(info)
        return self

    def to_xml(self):
        xml = util.ET.Element(self.meta.frame)
        xml.attrib['x'] = str(self.x)
        xml.attrib['y'] = str(self.y)
        xml.attrib['cdelay'] = str(self.cdelay)
        xml.attrib['ndelay'] = str(self.ndelay)
        xml.attrib['picIdx'] = str(self.picIdx)
        return xml

    def load_xml(self, xml):
        self.x = int(xml.attrib['x'])
        self.y = int(xml.attrib['y'])
        self.cdelay = int(xml.attrib['cdelay'])
        self.ndelay = int(xml.attrib['ndelay'])
        self.picIdx = int(xml.attrib['picIdx'])
        return self


class ImageSet:
    class meta:
        image = 'image'

    def __init__(self, data=None):
        self.width = 0
        self.height = 0
        self.count = 0
        self.mask = 0
        self.items = []
        if data is not None:
            self.load_data(data)

    def load_data(self, data):
        self.load_bin(structures.DataIO(data=data))
        return self

    def load_bin(self, io):
        info = io.read_struct(models.PICHEAD)
        self.__dict__.update(info)
        self.parse_data(io)
        return self

    def write_bin(self, io):
        io.write(self.bin())

    def parse_data(self, io):
        line_len = (self.width+7) // 8
        pic_len = line_len * self.height
        if self.mask & 1:
            data = io.read(pic_len*self.count*2)
        else:
            data = io.read(pic_len*self.count)
        self.items = util.chunk(data, pic_len)

    def image(self, index):
        return Image(self.width, self.height, self.items[index])

    def load_images(self, width, height, images):
        self.width = width
        self.height = height
        self.count = len(images)
        self.mask = 0
        for im in images:
            self.items.append(im.data)
        return self

    def bin(self):
        if self.count > 65535:
            raise Exception('单个图片集资源不能超过65535张')
        head = structures.write_struct(self.__dict__, models.PICHEAD)
        return b''.join([head] + self.items)

    def yield_images(self):
        count = len(self.items)
        if self.mask:
            count >>= 1
        for i in range(count):
            ind = i*2 if self.mask else i
            yield i, 0, self.image(ind)
            if self.mask:
                yield i, 1, self.image(ind+1)

    def to_xml(self):
        xml = util.ET.Element(self.meta.image)
        xml.attrib['width'] = str(scale(self.width))
        xml.attrib['height'] = str(scale(self.height))
        xml.attrib['count'] = str(self.count)
        xml.attrib['mask'] = str(self.mask)
        return xml

    def save_images(self, prefix):
        for i0, i1, img in self.yield_images():
            image_path = '{}{}_{}.bmp'.format(prefix, i0, i1)
            img.save(image_path)

    def save(self, resdir):
        raw = util.pretty_print_xml(self.to_xml())
        self.save_images(os.path.join(resdir, 'image') + '_')
        open(os.path.join(resdir, 'meta.xml'), 'wb').write(raw)

    def load_assets(self, resdir):
        xml = util.ET.fromstring(open(os.path.join(resdir, 'meta.xml')).read())
        self.load_xml(xml, os.path.join(resdir, 'image') + '_')
        return self

    def load_xml(self, xml, prefix):
        self.width = scale(int(xml.attrib['width']))
        self.height = scale(int(xml.attrib['height']))
        self.count = int(xml.attrib['count'])
        self.mask = int(xml.attrib['mask'])

        for i0 in range(self.count):
            subi = [0, 1] if self.mask else [0]
            for i1 in subi:
                image_path = '{}{}_{}.bmp'.format(prefix, i0, i1)
                img = Image.open(image_path, self.width, self.height)
                self.items.append(img.data)
        return self


class Image:
    def __init__(self, width, height, data=None):
        self.width = width
        self.height = height
        if data is None:
            line_len = (self.width+7) // 8
            data = b'0' * line_len * height
        self.data = data

    def to_bit_matrix(self):
        line_len = (self.width+7) // 8
        for line in util.chunk(self.data, line_len):
            bits = []
            for c in line:
                bits += util.byte_to_bits(c)
            yield bits[:self.width]

    def to_text(self):
        lines = []
        for line in self.to_bit_matrix():
            l = ''.join([c and '#' or '.' for c in line])
            lines.append(l)
        return '\n'.join(lines)

    def to_image(self):
        pimg = PImage.frombytes('1', (self.width, self.height), reverted_data(self.data))
        if input_scale != output_scale:
            pimg = pimg.resize((scale(self.width), scale(self.height)))
        return pimg

    def save(self, filename):
        dirname = os.path.dirname(filename)
        if not os.path.exists(dirname):
            os.makedirs(dirname)
        self.to_image().save(filename)

    @classmethod
    def from_image(cls, pimage, width, height):
        pimage = pimage.resize((width, height)).convert('1')
        data = pimage.tobytes()
        return Image(width, height, reverted_data(data))

    @classmethod
    def open(cls, filename, width, height):
        return cls.from_image(PImage.open(filename), width, height)


def reverted_data(data):
    return bytes([0xff & ~i for i in data])
